#include "table.h"
#include "qtch/log.h"
#include <set>

namespace qtch {
namespace orm {

static Logger::ptr logger = QTCH_LOG_NAME("orm");

bool Table::init(const tinyxml2::XMLElement& node){
    if(strcmp(node.Name(), "table") != 0){
        QTCH_LOG_ERROR(logger) << "node.name=" << node.Name() << " is not table";
        return false;
    }
    if(!node.Attribute("name")){
        QTCH_LOG_ERROR(logger) << "table name is null";
        return false;
    }
    m_name = node.Attribute("name");

    if(!node.Attribute("namespace")){
        QTCH_LOG_ERROR(logger) << "table name=" << m_name << " namespace is null";
        return false;
    }
    m_namespace = node.Attribute("namespace");

    if(node.Attribute("desc")){
        m_desc = node.Attribute("desc");
    }

    const tinyxml2::XMLElement* cols = node.FirstChildElement("columns");
    if(!cols){
        QTCH_LOG_ERROR(logger) << "table name=" << m_name << " columns is null";
        return false;
    }

    const tinyxml2::XMLElement* col = cols->FirstChildElement("column");
    if(!col){
        QTCH_LOG_ERROR(logger) << "table name=" << m_name << " column is null";
        return false;
    }

    int index = 0;
    std::set<std::string> col_name;
    do{
        Column::ptr col_ptr(new Column);
        if(!col_ptr->init(*col)){
            QTCH_LOG_ERROR(logger) << "table name=" << m_name << " init column name=" << col_ptr->getName()
                    << " error";
            return false;
        }
        if(col_name.insert(col_ptr->getName()).second == false){
            QTCH_LOG_ERROR(logger) << "table name=" << m_name << " init column name=" << col_ptr->getName()
                    << " exists";
            return false;
        }
        col_ptr->m_index = index++;
        m_cols.push_back(col_ptr);
        col = col->NextSiblingElement("column");

    }while(col);

    const tinyxml2::XMLElement* idxs = node.FirstChildElement("indexs");
    if(!idxs){
        QTCH_LOG_ERROR(logger) << "table name=" << m_name << " indexs is null";
        return false;
    }

    const tinyxml2::XMLElement* idx = idxs->FirstChildElement("index");
    if(!idx){
        QTCH_LOG_ERROR(logger) << "table name=" << m_name << " index is null";
        return false;
    }

    std::set<std::string> idx_name;
    bool has_PK = false;
    do{
        Index::ptr idx_ptr(new Index);
        if(!idx_ptr->init(*idx)){
            QTCH_LOG_ERROR(logger) << "table name=" << m_name << " init column name=" << idx_ptr->getName()
                    << " error";
            return false;
        }
        if(idx_name.insert(idx_ptr->getName()).second == false){
            QTCH_LOG_ERROR(logger) << "table name=" << m_name << " init column name=" << idx_ptr->getName()
                    << " exists";
            return false;
        }

        if(idx_ptr->isPK()){
            if(has_PK){
                QTCH_LOG_ERROR(logger) << "table name=" << m_name << " more then one pk";
                return false;
            }
            has_PK = true;
        }
        auto& cnames = idx_ptr->getCols();
        for(auto& x: cnames){
            if(col_name.count(x) == 0){
                QTCH_LOG_ERROR(logger) << "table name=" << m_name << " idx=" << idx_ptr->getName()
                    << " col=" << x << " not exists";
                return false;
             }
        }
        m_idxs.push_back(idx_ptr);
        idx = idx ->NextSiblingElement("index");

    }while(idx);
    

    const tinyxml2::XMLElement* mappers = node.FirstChildElement("mappers");
    if(mappers){
        const tinyxml2::XMLElement* crudNode = mappers->FirstChildElement();
        while(crudNode){
            if(strcasecmp(crudNode->Name(),"select")==0){
                Select::ptr select_ptr = Select::ptr(new Select(shared_from_this()));
                if(select_ptr->init(*crudNode)){
                    m_selsects.push_back(select_ptr);
                }
            } else if (strcasecmp(crudNode->Name(),"delete")==0){
                Delete::ptr delete_ptr = Delete::ptr(new Delete(shared_from_this()));
                if(delete_ptr->init(*crudNode)){
                    m_deletes.push_back(delete_ptr);
                }
            } else if (strcasecmp(crudNode->Name(),"update")==0){
                Update::ptr update_ptr = Update::ptr(new Update(shared_from_this()));
                if(update_ptr->init(*crudNode)){
                    m_updates.push_back(update_ptr);
                }
            } else if (strcasecmp(crudNode->Name(),"insert")==0){
                Insert::ptr insert_ptr = Insert::ptr(new Insert(shared_from_this()));
                if(insert_ptr->init(*crudNode)){
                    m_inserts.push_back(insert_ptr);
                }
            } 
            else {
                QTCH_LOG_ERROR(logger) << "table name=" << m_name << " unknow mapper name=" << crudNode->Name();
                return false;
            }
            crudNode = crudNode->NextSiblingElement();
        }
    }

    return true;
}

std::string Table::toString() const {
    std::stringstream ss;
    dump(ss);
    return ss.str();
}

std::ostream& Table::dump(std::ostream& os) const {
    os << "{table name=" << m_name << " namespace=" << m_namespace << " desc=" << m_desc
       << " subfix=" << m_subfix << " dbClass=" << m_dbClass << " queryClass=" << m_queryClass
       << " updateClass=" << m_updateClass <<"\n";
    os << "Indexs:\n";
    for(size_t i=0; i < m_idxs.size(); ++i){
        os << m_idxs[i] << "\n";
    }
    os << "Columns:\n";
    for(size_t i=0; i < m_cols.size(); ++i){
        os << m_cols[i] << "\n";
    }
    os << "]";
    return os;
}

std::string Table::getFileName() const {
    return qtch::StringUtil::toLower(m_name + m_subfix);
}

void Table::gen(const std::string& path){
    std::string namespacePath = StringUtil::replace(m_namespace,'.',"/");
    std::string newPath = path + (namespacePath.empty()? ""  : "/") +namespacePath;
    qtch::FSUtil::MkDir(newPath);
    gen_inc(newPath);
    gen_src(newPath);
}

void Table::gen_inc(const std::string& path) {
    std::string fileName = path + "/" + getFileName() + ".h";
    std::string class_name = getClassName();
    std::string class_name_dao = getClassDaoName();
    std::ofstream ofs(fileName);
    ofs << "#ifndef " << getAsDefeineMacro(m_namespace + class_name + ".h") << std::endl;
    ofs << "#define " << getAsDefeineMacro(m_namespace + class_name + ".h") << std::endl;
    ofs << std::endl;

    std::set<std::string> sincs = {
        "vector", 
        "string",
        "memory",
        "ostream",
        "sstream"
    };
    for(auto& i : sincs){
        ofs << "#include <" << i << ">" << std::endl;
    }

    std::set<std::string> incs = {
        "qtch/db/db.h",
        "qtch/util.h",
    };
    for(auto& i : incs){
        ofs << "#include \"" << i << "\"" << std::endl;
    }
    ofs << std::endl;

    std::vector<std::string> ns = qtch::StringUtil::split(m_namespace,'.');
    for(auto it = ns.begin(); it != ns.end(); ++it){
        ofs << "namespace " << *it << " {" << std::endl;
    }
    ofs << std::endl;


    gen_entity_inc(path,ofs);
    ofs << std::endl;
    ofs << std::endl;
    gen_dao_inc(path,ofs);

    ofs << std::endl;
    for(auto it = ns.begin(); it != ns.end(); ++it){
        ofs << "} // namespace " << *it << std::endl;
    }
    ofs << std::endl;


    ofs << "#endif" << std::endl;
}

void Table::gen_src(const std::string& path) {
    std::string fileName = path + "/" + getFileName() + ".cc";
    std::string class_name = getClassName();
    std::string class_name_dao = getClassDaoName();
    std::ofstream ofs(fileName);
    ofs << "#include \"" << getFileName() << ".h\"" << std::endl;
    ofs << std::endl;
    

    std::vector<std::string> ns = qtch::StringUtil::split(m_namespace,'.');
    for(auto it = ns.begin(); it != ns.end(); ++it){
        ofs << "namespace " << *it << " {" << std::endl;
    }
    ofs << std::endl;


    gen_entity_src(path,ofs);
    ofs << std::endl;
    ofs << std::endl;
    gen_dao_src(path,ofs);

    ofs << std::endl;
    for(auto it = ns.begin(); it != ns.end(); ++it){
        ofs << "} // namespace " << *it << std::endl;
    }





}

void Table::gen_entity_inc(const std::string& path, std::ostream& os) {
    std::string class_name = getClassName();
    std::string class_name_dao = getClassDaoName();
    os << "class " << class_name_dao << ";" << std::endl;
    os << "class " << class_name << " {" << std::endl;
    os << "friend class " << class_name_dao << ";" << std::endl;
    os << "public:" << std::endl;
    os << std::endl;

    os << "    typedef std::shared_ptr<" << class_name << "> ptr;" << std::endl;
    os << std::endl;

    os << "    " << class_name << "(){}" << std::endl;
    os << std::endl;

    for(size_t i=0; i < m_cols.size(); ++i){
        os << "    " << m_cols[i]->getGetFunDefine() << std::endl;
        os << "    " << m_cols[i]->getSetFunDefine() << std::endl;
        os << "    " << m_cols[i]->getIsFunDefine() << std::endl;
        os << "    " << m_cols[i]->getSetNullDefine() << std::endl;
        os << std::endl;
    }

    os << "private:" << std::endl;
    for(size_t i = 0; i < m_cols.size(); ++i){
        os << "    " << "/// " << m_cols[i]->getDesc() << std::endl;
        os << "    " << m_cols[i]->getMemberDefine() << std::endl;
        os << "    " << "bool " << GetAsMemberNameNull(m_cols[i]->m_name) << " = true;" << std::endl;
    }


    os << "};" << std::endl;
}

void Table::gen_entity_src(const std::string& path, std::ostream& os) {
    std::string class_name = getClassName();
    os << std::endl;
    for(size_t i = 0; i < m_cols.size(); ++i){
        os << m_cols[i]->getSetFunImpl(class_name) << std::endl;
        os << std::endl;
    }
}


void Table::gen_dao_inc(const std::string& path, std::ostream& os) {
    std::string class_name = getClassName();
    std::string class_name_dao = getClassDaoName();
    os << "class " << class_name_dao << " {" << std::endl;
    os << "public:" << std::endl;
    os << std::endl;
    os << "    typedef std::shared_ptr<" << class_name_dao << "> ptr;" << std::endl;
    os << std::endl;

    for(size_t i=0;i<m_selsects.size();++i){
        os << "    static " << m_selsects[i]->gen_inc() << ";" << std::endl;
    }

    for(size_t i=0;i<m_deletes.size();++i){
        os << "    static " << m_deletes[i]->gen_inc() << ";" << std::endl;
    }

    for(size_t i=0;i<m_updates.size();++i){
        os << "    static " << m_updates[i]->gen_inc() << ";" << std::endl;
    }
    
    for(size_t i=0;i<m_inserts.size();++i){
        os << "    static " << m_inserts[i]->gen_inc() << ";" << std::endl;
    }


    os << "};" << std::endl;
}

void Table::gen_dao_src(const std::string& path, std::ostream& os) {
    for(size_t i=0;i<m_selsects.size();++i){
        os << m_selsects[i]->gen_src() << std::endl;
        os << std::endl;
    }

    for(size_t i=0;i<m_deletes.size();++i){
        os << m_deletes[i]->gen_src() << std::endl;
        os << std::endl;
    }

    for(size_t i=0;i<m_updates.size();++i){
        os << m_updates[i]->gen_src() << std::endl;
        os << std::endl;
    }

    for(size_t i=0;i<m_inserts.size();++i){
        os << m_inserts[i]->gen_src() << std::endl;
        os << std::endl;
    }
}

const std::string& Table::getClassName() {
    if(m_class_entire_name != ""){
        return m_class_entire_name;
    }
    m_class_entire_name = GetAsClassName(m_name + m_subfix);
    return m_class_entire_name;
}

const std::string& Table::getClassDaoName() {
    if(m_class_dao_name != ""){
        return m_class_dao_name;
    }
    m_class_dao_name = GetAsClassName(m_name + m_subfix + "_dao");
    return m_class_dao_name;
}

















std::ostream& operator<<(std::ostream& os, const Table& table) {
    table.dump(os);
    return os;
}

std::ostream& operator<<(std::ostream& os, const Table::ptr table) {
    table->dump(os);
    return os;
}


}
}